﻿function jstat(){}
j = jstat;
/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
    var initializing = false, fnTest = /xyz/.test(function(){
        xyz;
    }) ? /\b_super\b/ : /.*/;
    // The base Class implementation (does nothing)
    this.Class = function(){};

    // Create a new Class that inherits from this class
    Class.extend = function(prop) {
        var _super = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;

        // Copy the properties over onto the new prototype
        for (var name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] == "function" &&
            typeof _super[name] == "function" && fnTest.test(prop[name]) ?
            (function(name, fn){
                return function() {
                    var tmp = this._super;

                    // Add a new ._super() method that is the same method
                    // but on the super-class
                    this._super = _super[name];

                    // The method only need to be bound temporarily, so we
                    // remove it when we're done executing
                    var ret = fn.apply(this, arguments);
                    this._super = tmp;

                    return ret;
                };
            })(name, prop[name]) :
            prop[name];
        }

        // The dummy class constructor
        function Class() {
            // All construction is actually done in the init method
            if ( !initializing && this.init )
                this.init.apply(this, arguments);
        }

        // Populate our constructed prototype object
        Class.prototype = prototype;

        // Enforce the constructor to be what we expect
        Class.constructor = Class;

        // And make this class extendable
        Class.extend = arguments.callee;

        return Class;
    };
})();

/******************************************************************************/
/*                           Constants                                        */
/******************************************************************************/
jstat.ONE_SQRT_2PI  =   0.3989422804014327;
jstat.LN_SQRT_2PI   =   0.9189385332046727417803297;
jstat.LN_SQRT_PId2  =   0.225791352644727432363097614947;
jstat.DBL_MIN       =   2.22507e-308;
jstat.DBL_EPSILON   =   2.220446049250313e-16;
jstat.SQRT_32       =   5.656854249492380195206754896838;
jstat.TWO_PI        =   6.283185307179586;
jstat.DBL_MIN_EXP   =   -999;
jstat.SQRT_2dPI     =   0.79788456080287;
jstat.LN_SQRT_PI    =   0.5723649429247;
/******************************************************************************/
/*                          jstat   Functions                                 */
/******************************************************************************/
jstat.seq = function(min, max, length) {
    var r = new Range(min, max, length);
    return r.getPoints();
}

jstat.dnorm = function(x, mean, sd, log) {
    if(mean == null) mean = 0;
    if(sd == null) sd = 1;
    if(log == null) log = false;
    var n = new NormalDistribution(mean, sd);
    if(!isNaN(x)) {
        // is a number
        return n._pdf(x, log);
    } else if(x.length) {
        var res = [];
        for(var i = 0; i < x.length; i++) {
            res.push(n._pdf(x[i], log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.pnorm = function(q, mean, sd, lower_tail, log) {
    if(mean == null) mean = 0;
    if(sd == null) sd = 1;
    if(lower_tail == null) lower_tail = true;
    if(log == null) log = false;

    var n = new NormalDistribution(mean, sd);
    if(!isNaN(q)) {
        // is a number
        return n._cdf(q, lower_tail, log);
    } else if(q.length) {
        var res = [];
        for(var i = 0; i < q.length; i++) {
            res.push(n._cdf(q[i], lower_tail, log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.dlnorm = function(x, meanlog, sdlog, log) {
    if(meanlog == null) meanlog = 0;
    if(sdlog == null) sdlog = 1;
    if(log == null) log = false;
    var n = new LogNormalDistribution(meanlog, sdlog);
    if(!isNaN(x)) {
        // is a number
        return n._pdf(x, log);
    } else if(x.length) {
        var res = [];
        for(var i = 0; i < x.length; i++) {
            res.push(n._pdf(x[i], log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.plnorm = function(q, meanlog, sdlog, lower_tail, log) {
    if(meanlog == null) meanlog = 0;
    if(sdlog == null) sdlog = 1;
    if(lower_tail == null) lower_tail = true;
    if(log == null) log = false;

    var n = new LogNormalDistribution(meanlog, sdlog);
    if(!isNaN(q)) {
        // is a number
        return n._cdf(q, lower_tail, log);
    }
    else if(q.length) {
        var res = [];
        for(var i = 0; i < q.length; i++) {
            res.push(n._cdf(q[i], lower_tail, log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.dbeta = function(x, alpha, beta, ncp, log) {
    if(ncp == null) ncp = 0;
    if(log == null) log = false;
    var b = new BetaDistribution(alpha, beta);
    if(!isNaN(x)) {
        // is a number
        return b._pdf(x, log);
    }
    else if(x.length) {
        var res = [];
        for(var i = 0; i < x.length; i++) {
            res.push(b._pdf(x[i], log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.pbeta = function(q, alpha, beta, ncp, lower_tail, log) {
    if(ncp == null) ncp = 0;
    if(log == null) log = false;
    if(lower_tail == null) lower_tail = true;

    var b = new BetaDistribution(alpha, beta);
    if(!isNaN(q)) {
        // is a number
        return b._cdf(q, lower_tail, log);
    } else if(q.length) {
        var res = [];
        for(var i = 0; i < q.length; i++) {
            res.push(b._cdf(q[i], lower_tail, log));
        }
        return res;
    }
    else {
        throw "Illegal argument: x";
    }
}

jstat.dgamma = function(x, shape, rate, scale, log) {
    if(rate == null) rate = 1;
    if(scale == null) scale = 1/rate;
    if(log == null) log = false;

    var g = new GammaDistribution(shape, scale);
    if(!isNaN(x)) {
        // is a number
        return g._pdf(x, log);
    } else if(x.length) {
        var res = [];
        for(var i = 0; i < x.length; i++) {
            res.push(g._pdf(x[i], log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }
}

jstat.pgamma = function(q, shape, rate, scale, lower_tail, log) {
    if(rate == null) rate = 1;
    if(scale == null) scale = 1/rate;
    if(lower_tail == null) lower_tail = true;
    if(log == null) log = false;

    var g = new GammaDistribution(shape, scale);
    if(!isNaN(q)) {
        // is a number
        return g._cdf(q, lower_tail, log);
    } else if(q.length) {
        var res = [];
        for(var i = 0; i < q.length; i++) {
            res.push(g._cdf(q[i], lower_tail, log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }

}

jstat.dt = function(x, df, ncp, log) {
    if(log == null) log = false;

    var t = new StudentTDistribution(df, ncp);
    if(!isNaN(x)) {
        // is a number
        return t._pdf(x, log);
    } else if(x.length) {
        var res = [];
        for(var i = 0; i < x.length; i++) {
            res.push(t._pdf(x[i], log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }

}

jstat.pt = function(q, df, ncp, lower_tail, log) {
    if(lower_tail == null) lower_tail = true;
    if(log == null) log = false;

    var t = new StudentTDistribution(df, ncp);
    if(!isNaN(q)) {
        // is a number
        return t._cdf(q, lower_tail, log);
    } else if(q.length) {
        var res = [];
        for(var i = 0; i < q.length; i++) {
            res.push(t._cdf(q[i], lower_tail, log));
        }
        return res;
    } else {
        throw "Illegal argument: x";
    }

}

jstat.plot = function(x, y, options) {
    if(x == null) {
        throw "x is undefined in jstat.plot";
    }
    if(y == null) {
        throw "y is undefined in jstat.plot";
    }
    if(x.length != y.length) {
        throw "x and y lengths differ in jstat.plot";
    }

    var flotOpt = {
        series: {
            lines: {

            },
            points: {

        }
        }
    };

    // combine x & y
    var series = [];
    if(x.length == undefined) {
        // single point
        series.push([x, y]);
        flotOpt.series.points.show = true;
    } else {
        // array
        for(var i = 0; i < x.length; i++) {
            series.push([x[i], y[i]]);
        }
    }

    var title = 'jstat graph';

    // configure Flot options
    if(options != null) {
        // options = JSON.parse(String(options));
        if(options.type != null) {
            if(options.type == 'l') {
                flotOpt.series.lines.show =  true;
            } else if (options.type == 'p') {
                flotOpt.series.lines.show = false;
                flotOpt.series.points.show = true;
            }
        }
        if(options.hover != null) {
            flotOpt.grid = {
                hoverable: options.hover
            }
        }

        if(options.main != null) {
            title = options.main;
        }
    }
    var now = new Date();
    var hash = now.getMilliseconds() * now.getMinutes() + now.getSeconds();
    $('body').append('<div title="' + title + '" style="display: none;" id="'+ hash +'"><div id="graph-' + hash + '" style="width:95%; height: 95%"></div></div>');

    $('#' + hash).dialog({
        modal: false,
        width: 475,
        height: 475,
        resizable: true,
        resize: function() {
            $.plot($('#graph-' + hash), [series], flotOpt);
        },
        open: function(event, ui) {
            var id = '#graph-' + hash;
            $.plot($('#graph-' + hash), [series], flotOpt);
        }
    })
}

/******************************************************************************/
/*                          Special Functions                                 */
/******************************************************************************/

jstat.log10 = function(arg) {
    return Math.log(arg) / Math.LN10;
}

/*
 *
 */
jstat.toSigFig = function(num, n) {
    if(num == 0) {
        return 0;
    }
    var d = Math.ceil(jstat.log10(num < 0 ? -num: num));
    var power = n - parseInt(d);
    var magnitude = Math.pow(10,power);
    var shifted = Math.round(num*magnitude);
    return shifted/magnitude;
}

jstat.trunc = function(x) {
    return (x > 0) ? Math.floor(x) : Math.ceil(x);
}

/**
 *  Tests whether x is a finite number
 */
jstat.isFinite = function(x) {
    return (!isNaN(x) && (x != Number.POSITIVE_INFINITY) && (x != Number.NEGATIVE_INFINITY));
}

/**
 *      dopois_raw() computes the Poisson probability  lb^x exp(-lb) / x!.
 *      This does not check that x is an integer, since dgamma() may
 *      call this with a fractional x argument. Any necessary argument
 *      checks should be done in the calling function.
 */
jstat.dopois_raw = function(x, lambda, give_log) {
    /*       x >= 0 ; integer for dpois(), but not e.g. for pgamma()!
        lambda >= 0
     */
    if (lambda == 0) {
        if(x == 0) {
            return(give_log) ? 0.0 : 1.0; //R_D__1
        }
        return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; // R_D__0
    }
    if (!jstat.isFinite(lambda)) return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; //R_D__0;
    if (x < 0) return(give_log) ? Number.NEGATIVE_INFINITY : 0.0; //R_D__0
    if (x <= lambda * jstat.DBL_MIN) {
        return (give_log) ? -lambda : Math.exp(-lambda);    // R_D_exp(-lambda)
    }
    if (lambda < x * jstat.DBL_MIN) {
        var param = -lambda + x*Math.log(lambda) -jstat.lgamma(x+1);
        return (give_log) ? param : Math.exp(param);    // R_D_exp(-lambda + x*log(lambda) -lgammafn(x+1))
    }
    var param1 = jstat.TWO_PI * x;  // f
    var param2 = -jstat.stirlerr(x)-jstat.bd0(x,lambda);    // x
    return (give_log) ? -0.5*Math.log(param1)+param2 : Math.exp(param2)/Math.sqrt(param1);  // R_D_fexp(M_2PI*x, -stirlerr(x)-bd0(x,lambda))
//return(R_D_fexp( , -stirlerr(x)-bd0(x,lambda) ));
}

/**	Evaluates the "deviance part"
 *	bd0(x,M) :=  M * D0(x/M) = M*[ x/M * log(x/M) + 1 - (x/M) ] =
 *		  =  x * log(x/M) + M - x
 *	where M = E[X] = n*p (or = lambda), for	  x, M > 0
 *
 *	in a manner that should be stable (with small relative error)
 *	for all x and M=np. In particular for x/np close to 1, direct
 *	evaluation fails, and evaluation is based on the Taylor series
 *	of log((1+v)/(1-v)) with v = (x-np)/(x+np).
 */
jstat.bd0 = function(x, np) {
    var ej, s, s1, v, j;
    if(!jstat.isFinite(x) || !jstat.isFinite(np) || np == 0.0) throw "illegal parameter in jstat.bd0";

    if(Math.abs(x-np) > 0.1*(x+np)) {
        v = (x-np)/(x+np);
        s = (x-np)*v;/* s using v -- change by MM */
        ej = 2*x*v;
        v = v*v;
        for (j=1; ; j++) { /* Taylor series */
            ej *= v;
            s1 = s+ej/((j<<1)+1);
            if (s1==s) /* last term was effectively 0 */
                return(s1);
            s = s1;
        }
    }
    /* else:  | x - np |  is not too small */
    return(x*Math.log(x/np)+np-x);
}

/**    Computes the log of the error term in Stirling's formula.
 *      For n > 15, uses the series 1/12n - 1/360n^3 + ...
 *      For n <=15, integers or half-integers, uses stored values.
 *      For other n < 15, uses lgamma directly (don't use this to
 *        write lgamma!)
 */
jstat.stirlerr= function(n) {
    var S0 = 0.083333333333333333333;
    var S1 = 0.00277777777777777777778;
    var S2 = 0.00079365079365079365079365;
    var S3 = 0.000595238095238095238095238;
    var S4 = 0.0008417508417508417508417508;

    var sferr_halves = [
    0.0, /* n=0 - wrong, place holder only */
    0.1534264097200273452913848,  /* 0.5 */
    0.0810614667953272582196702,  /* 1.0 */
    0.0548141210519176538961390,  /* 1.5 */
    0.0413406959554092940938221,  /* 2.0 */
    0.03316287351993628748511048, /* 2.5 */
    0.02767792568499833914878929, /* 3.0 */
    0.02374616365629749597132920, /* 3.5 */
    0.02079067210376509311152277, /* 4.0 */
    0.01848845053267318523077934, /* 4.5 */
    0.01664469118982119216319487, /* 5.0 */
    0.01513497322191737887351255, /* 5.5 */
    0.01387612882307074799874573, /* 6.0 */
    0.01281046524292022692424986, /* 6.5 */
    0.01189670994589177009505572, /* 7.0 */
    0.01110455975820691732662991, /* 7.5 */
    0.010411265261972096497478567, /* 8.0 */
    0.009799416126158803298389475, /* 8.5 */
    0.009255462182712732917728637, /* 9.0 */
    0.008768700134139385462952823, /* 9.5 */
    0.008330563433362871256469318, /* 10.0 */
    0.007934114564314020547248100, /* 10.5 */
    0.007573675487951840794972024, /* 11.0 */
    0.007244554301320383179543912, /* 11.5 */
    0.006942840107209529865664152, /* 12.0 */
    0.006665247032707682442354394, /* 12.5 */
    0.006408994188004207068439631, /* 13.0 */
    0.006171712263039457647532867, /* 13.5 */
    0.005951370112758847735624416, /* 14.0 */
    0.005746216513010115682023589, /* 14.5 */
    0.005554733551962801371038690  /* 15.0 */
    ];

    var nn;

    if (n <= 15.0) {
        nn = n + n;
        if (nn == parseInt(nn)) return(sferr_halves[parseInt(nn)]);
        return(jstat.lgamma(n + 1.0) - (n + 0.5)*Math.log(n) + n - jstat.LN_SQRT_2PI);
    }

    nn = n*n;
    if (n>500) return((S0-S1/nn)/n);
    if (n> 80) return((S0-(S1-S2/nn)/nn)/n);
    if (n> 35) return((S0-(S1-(S2-S3/nn)/nn)/nn)/n);
    /* 15 < n <= 35 : */
    return((S0-(S1-(S2-(S3-S4/nn)/nn)/nn)/nn)/n);
}



/**    The function lgamma computes log|gamma(x)|.  The function
 *    lgammafn_sign in addition assigns the sign of the gamma function
 *    to the address in the second argument if this is not null.
 */
jstat.lgamma = function(x) {
    function lgammafn_sign(x, sgn) {
        var ans, y, sinpiy;
        var xmax = 2.5327372760800758e+305;
        var dxrel = 1.490116119384765696e-8;

        // if (xmax == 0) {/* initialize machine dependent constants _ONCE_ */
        //     xmax = jstat.DBL_MAX/Math.log(jstat.DBL_MAX);/* = 2.533 e305	 for IEEE double */
        //     dxrel = Math.sqrt(jstat.DBL_EPSILON);/* sqrt(Eps) ~ 1.49 e-8  for IEEE double */
        // }

        /* For IEEE double precision DBL_EPSILON = 2^-52 = 2.220446049250313e-16 :
           xmax  = DBL_MAX / log(DBL_MAX) = 2^1024 / (1024 * log(2)) = 2^1014 / log(2)
           dxrel = sqrt(DBL_EPSILON) = 2^-26 = 5^26 * 1e-26 (is *exact* below !)
         */

        if (sgn != null) sgn = 1;

        if(isNaN(x)) return x;

        if (x < 0 && (Math.floor(-x) % 2.0) == 0)
            if (sgn != null) sgn = -1;

        if (x <= 0 && x == jstat.trunc(x)) { /* Negative integer argument */
            console.warn("Negative integer argument in lgammafn_sign");
            return Number.POSITIVE_INFINITY;/* +Inf, since lgamma(x) = log|gamma(x)| */
        }

        y = Math.abs(x);

        if(y <= 10) return Math.log(Math.abs(jstat.gamma(x)));  // TODO: implement jstat.gamma

        if(y > xmax) {
            console.warn("Illegal arguement passed to lgammafn_sign");
            return Number.POSITIVE_INFINITY;
        }

        if(x > 0) {
            if(x > 1e17) {
                return (x*(Math.log(x)-1.0));
            } else if(x > 4934720.0) {
                return (jstat.LN_SQRT_2PI + (x-0.5) * Math.log(x) - x);
            } else {
                return jstat.LN_SQRT_2PI + (x-0.5) * Math.log(x) - x + jstat.lgammacor(x);  // TODO: implement lgammacor
            }
        }

        sinpiy = Math.abs(Math.sin(Math.PI * y));

        if(sinpiy == 0) {
            throw "Should never happen!!";
        }

        ans = jstat.LN_SQRT_PId2 + (x - 0.5) * Math.log(y) - x - Math.log(sinpiy) - jstat.lgammacor(y);

        if(Math.abs((x-jstat.trunc(x-0.5))* ans / x) < dxrel) {
            throw "The answer is less than half the precision argument too close to a negative integer";
        }
        return ans;
    }

    return lgammafn_sign(x, null);
}

jstat.gamma = function(x) {
    var xbig = 171.624;
    var p = [
    -1.71618513886549492533811,
    24.7656508055759199108314,-379.804256470945635097577,
    629.331155312818442661052,866.966202790413211295064,
    -31451.2729688483675254357,-36144.4134186911729807069,
    66456.1438202405440627855
    ];
    var q = [
    -30.8402300119738975254353,
    315.350626979604161529144,-1015.15636749021914166146,
    -3107.77167157231109440444,22538.1184209801510330112,
    4755.84627752788110767815,-134659.959864969306392456,
    -115132.259675553483497211
    ];
    var c = [
    -.001910444077728,8.4171387781295e-4,
    -5.952379913043012e-4,7.93650793500350248e-4,
    -.002777777777777681622553,.08333333333333333331554247,
    .0057083835261
    ];

    var i,n,parity,fact,xden,xnum,y,z,yi,res,sum,ysq;

    parity = (0);
    fact = 1.0;
    n = 0;
    y=x;
    if(y <= 0.0) {
        /* -------------------------------------------------------------
	   Argument is negative
	   ------------------------------------------------------------- */
        y = -x;
        yi = jstat.trunc(y);
        res = y - yi;
        if (res != 0.0) {
            if (yi != jstat.trunc(yi * 0.5) * 2.0)
                parity = (1);
            fact = -Math.PI / Math.sin(Math.PI * res);
            y += 1.0;
        } else {
            return(Number.POSITIVE_INFINITY);
        }
    }
    /* -----------------------------------------------------------------
       Argument is positive
       -----------------------------------------------------------------*/
    if (y < jstat.DBL_EPSILON) {
        /* --------------------------------------------------------------
	   Argument < EPS
	   -------------------------------------------------------------- */
        if (y >= jstat.DBL_MIN) {
            res = 1.0 / y;
        } else {
            return(Number.POSITIVE_INFINITY);
        }
    } else if (y < 12.0) {
        yi = y;
        if (y < 1.0) {
            /* ---------------------------------------------------------
	       EPS < argument < 1
	       --------------------------------------------------------- */
            z = y;
            y += 1.0;
        } else {
            /* -----------------------------------------------------------
	       1 <= argument < 12, reduce argument if necessary
	       ----------------------------------------------------------- */
            n = parseInt(y) - 1;
            y -= parseFloat(n);
            z = y - 1.0;
        }
        /* ---------------------------------------------------------
	   Evaluate approximation for 1. < argument < 2.
	   ---------------------------------------------------------*/
        xnum = 0.0;
        xden = 1.0;
        for (i = 0; i < 8; ++i) {
            xnum = (xnum + p[i]) * z;
            xden = xden * z + q[i];
        }
        res = xnum / xden + 1.0;
        if (yi < y) {
            /* --------------------------------------------------------
	       Adjust result for case  0. < argument < 1.
	       -------------------------------------------------------- */
            res /= yi;
        } else if (yi > y) {
            /* ----------------------------------------------------------
	       Adjust result for case  2. < argument < 12.
	       ---------------------------------------------------------- */
            for (i = 0; i < n; ++i) {
                res *= y;
                y += 1.0;
            }
        }
    } else {
        /* -------------------------------------------------------------
	   Evaluate for argument >= 12.,
	   ------------------------------------------------------------- */
        if (y <= xbig) {
            ysq = y * y;
            sum = c[6];
            for (i = 0; i < 6; ++i) {
                sum = sum / ysq + c[i];
            }
            sum = sum / y - y + jstat.LN_SQRT_2PI;
            sum += (y - 0.5) * Math.log(y);
            res = Math.exp(sum);
        } else {
            return(Number.POSITIVE_INFINITY);
        }
    }
    /* ----------------------------------------------------------------------
       Final adjustments and return
       ----------------------------------------------------------------------*/
    if (parity)
        res = -res;
    if (fact != 1.0)
        res = fact / res;
    return res;
}

/**    Compute the log gamma correction factor for x >= 10 so that
 *
 *    log(gamma(x)) = .5*log(2*pi) + (x-.5)*log(x) -x + lgammacor(x)
 *
 *    [ lgammacor(x) is called	Del(x)	in other contexts (e.g. dcdflib)]
 */
jstat.lgammacor = function(x) {
    var algmcs = [
    +.1666389480451863247205729650822e+0,
    -.1384948176067563840732986059135e-4,
    +.9810825646924729426157171547487e-8,
    -.1809129475572494194263306266719e-10,
    +.6221098041892605227126015543416e-13,
    -.3399615005417721944303330599666e-15,
    +.2683181998482698748957538846666e-17,
    -.2868042435334643284144622399999e-19,
    +.3962837061046434803679306666666e-21,
    -.6831888753985766870111999999999e-23,
    +.1429227355942498147573333333333e-24,
    -.3547598158101070547199999999999e-26,
    +.1025680058010470912000000000000e-27,
    -.3401102254316748799999999999999e-29,
    +.1276642195630062933333333333333e-30
    ];

    var tmp;
    var nalgm = 5;
    var xbig = 94906265.62425156;
    var xmax = 3.745194030963158e306;

    if(x < 10) {
        return Number.NaN;
    } else if (x >= xmax) {
        throw "Underflow error in lgammacor";
    } else if (x < xbig) {
        tmp = 10 / x;
        return jstat.chebyshev(tmp*tmp*2-1,algmcs,nalgm) / x;
    }
    return 1 / (x*12);
}

/*
 * Incomplete Beta function
 */
jstat.incompleteBeta = function(a, b, x) {
    /*
     * Used by incompleteBeta: Evaluates continued fraction for incomplete
     * beta function by modified Lentz's method.
     */
    function betacf(a, b, x) {
        var MAXIT = 100;
        var EPS = 3.0e-12;
        var FPMIN = 1.0e-30;

        var m,m2,aa,c,d,del,h,qab,qam,qap;

        qab=a+b;
        qap=a+1.0;
        qam=a-1.0;
        c=1.0;
        d=1.0-qab*x/qap;

        if(Math.abs(d) < FPMIN) {
            d=FPMIN;
        }

        d = 1.0/d;
        h=d;
        for(m = 1; m <= MAXIT; m++) {
            m2=2*m;
            aa=m*(b-m)*x/((qam+m2)*(a+m2));
            d=1.0+aa*d;
            if(Math.abs(d) < FPMIN) {
                d = FPMIN;
            }
            c=1.0+aa/c;
            if(Math.abs(c) < FPMIN) {
                c = FPMIN;
            }
            d=1.0/d;
            h *= d*c;
            aa = -(a+m)*(qab+m)*x/((a+m2) * (qap+m2));
            d=1.0+aa*d;
            if(Math.abs(d) < FPMIN) {
                d = FPMIN;
            }
            c=1.0+aa/c;
            if(Math.abs(c) < FPMIN) {
                c=FPMIN;
            }
            d=1.0/d;
            del=d*c;
            h *= del;
            if(Math.abs(del-1.0) < EPS) {
                // are we done?
                break;
            }
        }
        if(m > MAXIT) {
            console.warn("a or b too big, or MAXIT too small in betacf: " + a + ", " + b + ", " + x + ", " + h);
            return h;
        }
        if(isNaN(h)) {
            console.warn(a + ", " + b + ", " + x);
        }
        return h;
    }

    var bt;

    if(x < 0.0 || x > 1.0) {
        throw "bad x in routine incompleteBeta";
    }
    if(x == 0.0 || x == 1.0) {
        bt = 0.0;
    } else {
        bt = Math.exp(jstat.lgamma(a+b) - jstat.lgamma(a) - jstat.lgamma(b) + a * Math.log(x)+ b * Math.log(1.0-x));
    }
    if(x < (a + 1.0)/(a+b+2.0)) {
        return bt * betacf(a,b,x)/a;
    } else {
        return 1.0-bt*betacf(b,a,1.0-x)/b;
    }
}

/**   Evaluates the n-term Chebyshev series
 *    "a" at "x".
 */
jstat.chebyshev = function(x, a, n) {
    var b0, b1, b2, twox;
    var i;

    if (n < 1 || n > 1000) return Number.NaN;

    if (x < -1.1 || x > 1.1) return Number.NaN;

    twox = x * 2;
    b2 = b1 = 0;
    b0 = 0;
    for (i = 1; i <= n; i++) {
        b2 = b1;
        b1 = b0;
        b0 = twox * b1 - b2 + a[n - i];
    }
    return (b0 - b2) * 0.5;
}

jstat.fmin2 = function(x, y) {
    return (x < y) ? x : y;
}

jstat.log1p = function(x) {
    // http://kevin.vanzonneveld.net
    // +   original by: Brett Zamir (http://brett-zamir.me)
    // %          note 1: Precision 'n' can be adjusted as desired
    // *     example 1: log1p(1e-15);
    // *     returns 1: 9.999999999999995e-16

    var ret = 0,
    n = 50; // degree of precision
    if (x <= -1) {
        return Number.NEGATIVE_INFINITY; // JavaScript style would be to return Number.NEGATIVE_INFINITY
    }
    if (x < 0 || x > 1) {
        return Math.log(1 + x);
    }
    for (var i = 1; i < n; i++) {
        if ((i % 2) === 0) {
            ret -= Math.pow(x, i) / i;
        } else {
            ret += Math.pow(x, i) / i;
        }
    }
    return ret;
}

jstat.expm1 = function(x) {
    var y, a  = Math.abs(x);
    if(a < jstat.DBL_EPSILON) return x;
    if(a > 0.697) return Math.exp(x) - 1; /* negligable cancellation */
    if(a > 1e-8) {
        y = Math.exp(x) - 1;
    } else {
        y = (x / 2 + 1) * x;
    }

    /* Newton step for solving   log(1 + y) = x   for y : */
    /* WARNING: does not work for y ~ -1: bug in 1.5.0 */
    y -= (1 + y) * (jstat.log1p(y) - x);
    return y;
}

jstat.logBeta = function(a, b) {
    var corr, p, q;
    p = q = a;
    if(b < p) p = b;/* := min(a,b) */
    if(b > q) q = b;/* := max(a,b) */

    /* both arguments must be >= 0 */
    if (p < 0) {
        console.warn('Both arguements must be >= 0');
        return Number.NaN;
    }
    else if (p == 0) {
        return Number.POSITIVE_INFINITY;
    }
    else if (!jstat.isFinite(q)) { /* q == +Inf */
        return Number.NEGATIVE_INFINITY;
    }

    if (p >= 10) {
        /* p and q are big. */
        corr = jstat.lgammacor(p) + jstat.lgammacor(q) - jstat.lgammacor(p + q);
        return Math.log(q) * -0.5 + jstat.LN_SQRT_2PI + corr
        + (p - 0.5) * Math.log(p / (p + q)) + q * jstat.log1p(-p / (p + q));
    }
    else if (q >= 10) {
        /* p is small, but q is big. */
        corr = jstat.lgammacor(q) - jstat.lgammacor(p + q);
        return jstat.lgamma(p) + corr + p - p * Math.log(p + q)
        + (q - 0.5) * jstat.log1p(-p / (p + q));
    }
    else
        /* p and q are small: p <= q < 10. */
        return Math.log(jstat.gamma(p) * (jstat.gamma(q) / jstat.gamma(p + q)));
}

jstat.dbinom_raw = function(x, n, p, q, give_log) {
    if(give_log == null) give_log = false;
    var lf, lc;

    if(p == 0) {
        if(x == 0) {
            // R_D__1
            return (give_log) ? 0.0 : 1.0;
        } else {
            // R_D__0
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
        }
    }
    if(q == 0) {
        if(x == n) {
            // R_D__1
            return (give_log) ? 0.0 : 1.0;
        } else {
            // R_D__0
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
        }
    }

    if (x == 0) {
        if(n == 0) return (give_log) ? 0.0 : 1.0;   //R_D__1;
        lc = (p < 0.1) ? -jstat.bd0(n,n*q) - n*p : n*Math.log(q);
        return ( give_log ) ? lc : Math.exp(lc); //R_D_exp(lc)
    }

    if (x == n) {
        lc = (q < 0.1) ? -jstat.bd0(n,n*p) - n*q : n*Math.log(p);
        return ( give_log ) ? lc : Math.exp(lc); //R_D_exp(lc)
    }

    if (x < 0 || x > n) return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; // R_D__0;

    /* n*p or n*q can underflow to zero if n and p or q are small.  This
       used to occur in dbeta, and gives NaN as from R 2.3.0.  */
    lc = jstat.stirlerr(n) - jstat.stirlerr(x) - jstat.stirlerr(n-x) - jstat.bd0(x,n*p) - jstat.bd0(n-x,n*q);

    /* f = (M_2PI*x*(n-x))/n; could overflow or underflow */
    /* Upto R 2.7.1:
     * lf = log(M_2PI) + log(x) + log(n-x) - log(n);
     * -- following is much better for  x << n : */
    lf = Math.log(jstat.TWO_PI) + Math.log(x) + jstat.log1p(- x/n);

    return (give_log) ? lc - 0.5*lf : Math.exp(lc - 0.5*lf); // R_D_exp(lc - 0.5*lf);
}

jstat.max = function(values) {
    var max = Number.NEGATIVE_INFINITY;
    for(var i = 0; i < values.length; i++) {
        if(values[i] > max) {
            max = values[i];
        }
    }
    return max;
}

/******************************************************************************/
/*                      Probability Distributions                             */
/******************************************************************************/

/**
 * Range class
 */
var Range = Class.extend({
    init: function(min, max, numPoints) {
        this._minimum = parseFloat(min);
        this._maximum = parseFloat(max);
        this._numPoints = parseFloat(numPoints);
    },
    getMinimum: function() {
        return this._minimum;
    },
    getMaximum: function() {
        return this._maximum;
    },
    getNumPoints: function() {
        return this._numPoints;
    },
    getPoints: function() {
        var results = [];
        var x = this._minimum;
        var step = (this._maximum-this._minimum)/(this._numPoints-1);
        for(var i = 0; i < this._numPoints; i++) {
            results[i] = parseFloat(x.toFixed(6));
            x += step;
        }
        return results;
    }
});

Range.validate = function(range) {
    if( ! range instanceof Range) {
        return false;
    }
    if(isNaN(range.getMinimum()) || isNaN(range.getMaximum()) || isNaN(range.getNumPoints()) || range.getMaximum() < range.getMinimum() || range.getNumPoints() <= 0) {
        return false;
    }
    return true;
}

var ContinuousDistribution = Class.extend({
    init: function(name) {
        this._name = name;
    },
    toString: function() {
        return this._string;
    },
    getName: function() {
        return this._name;
    },
    getClassName: function() {
        return this._name + 'Distribution';
    },
    density: function(valueOrRange) {
        if(!isNaN(valueOrRange)) {
            // single value
            return parseFloat(this._pdf(valueOrRange).toFixed(15));
        } else if (Range.validate(valueOrRange)) {
            // multiple values
            var points = valueOrRange.getPoints();

            var result = [];
            // For each point in the range
            for(var i = 0; i < points.length; i++) {
                result[i] = parseFloat(this._pdf(points[i]));
            }
            return result;
        } else {
            // neither value or range
            throw "Invalid parameter supplied to " + this.getClassName() + ".density()";
        }
    },
    cumulativeDensity: function(valueOrRange) {
        if(!isNaN(valueOrRange)) {
            // single value
            return parseFloat(this._cdf(valueOrRange).toFixed(15));
        } else if (Range.validate(valueOrRange)) {
            // multiple values
            var points = valueOrRange.getPoints();
            var result = [];
            // For each point in the range
            for(var i = 0; i < points.length; i++) {
                result[i] = parseFloat(this._cdf(points[i]));
            }
            return result;
        } else {
            // neither value or range
            throw "Invalid parameter supplied to " + this.getClassName() + ".cumulativeDensity()";
        }
    },
    getRange: function(standardDeviations, numPoints) {
        if(standardDeviations == null) {
            standardDeviations = 5;
        }
        if(numPoints == null) {
            numPoints = 100;
        }
        var min = this.getMean() - standardDeviations * Math.sqrt(this.getVariance());
        var max = this.getMean() + standardDeviations * Math.sqrt(this.getVariance());

        if(this.getClassName() == 'GammaDistribution' || this.getClassName() == 'LogNormalDistribution') {
            min = 0.0;
            max = this.getMean() + standardDeviations * Math.sqrt(this.getVariance());
        } else if(this.getClassName() == 'BetaDistribution') {
            min = 0.0;
            max = 1.0;
        }


        var range = new Range(min, max, numPoints);
        return range;
    },
    getVariance: function(){},
    getMean: function(){},
    getQuantile: function(p) {
        var self = this;
        /*
         *  Recursive function to find the closest match
         */
        function findClosestMatch(range, p) {
            var ERR = 1.0e-5;
            var xs = range.getPoints();
            var closestIndex = 0;
            var closestDistance = 999;

            for(var i=0; i<xs.length; i++) {
                var pp = self.cumulativeDensity(xs[i]);
                var distance = Math.abs(pp - p);
                if(distance < closestDistance) {
                    // closer value found
                    closestIndex = i;
                    closestDistance = distance;
                }
            }
            if(closestDistance <= ERR) {
                // Acceptable - return value;
                return xs[closestIndex];
            } else {
                // Calculate the new range
                var newRange = new Range(xs[closestIndex-1], xs[closestIndex+1],20);
                return findClosestMatch(newRange, p);
            }
        }
        var range = this.getRange(5, 20);
        return findClosestMatch(range, p);
    }
});

/**
 * A normal distribution object
 */
var NormalDistribution = ContinuousDistribution.extend({
    init: function(mean, sigma) {
        this._super('Normal');
        this._mean = parseFloat(mean);
        this._sigma = parseFloat(sigma);
        this._string = "Normal ("+this._mean.toFixed(2)+", " + this._sigma.toFixed(2) + ")";
    },
    _pdf: function(x, give_log) {
        if(give_log == null) {
            give_log=false;
        }  // default is false;
        var sigma = this._sigma;
        var mu = this._mean;
        if(!jstat.isFinite(sigma)) {
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0
        }
        if(!jstat.isFinite(x) && mu == x) {
            return Number.NaN;
        }
        if(sigma<=0) {
            if(sigma < 0) {
                throw "invalid sigma in _pdf";
            }
            return (x==mu)?Number.POSITIVE_INFINITY:(give_log)?Number.NEGATIVE_INFINITY:0.0;
        }
        x=(x-mu)/sigma;
        if(!jstat.isFinite(x)){
            return (give_log)?Number.NEGATIVE_INFINITY:0.0;
        }
        return (give_log ? -(jstat.LN_SQRT_2PI + 0.5 * x * x + Math.log(sigma)) :
            jstat.ONE_SQRT_2PI * Math.exp(-0.5 * x * x) / sigma);
    },
    _cdf: function(x, lower_tail, log_p) {

        if(lower_tail == null) lower_tail = true;
        if(log_p == null) log_p = false;

        function pnorm_both(x, cum, ccum, i_tail, log_p) {
            /*  i_tail in {0,1,2} means: "lower", "upper", or "both" :
                if(lower) return  *cum := P[X <= x]
                if(upper) return *ccum := P[X >  x] = 1 - P[X <= x]
             */

            var a = [
            2.2352520354606839287,
            161.02823106855587881,
            1067.6894854603709582,
            18154.981253343561249,
            0.065682337918207449113
            ];
            var b = [
            47.20258190468824187,
            976.09855173777669322,
            10260.932208618978205,
            45507.789335026729956
            ];
            var c = [
            0.39894151208813466764,
            8.8831497943883759412,
            93.506656132177855979,
            597.27027639480026226,
            2494.5375852903726711,
            6848.1904505362823326,
            11602.651437647350124,
            9842.7148383839780218,
            1.0765576773720192317e-8
            ];
            var d = [
            22.266688044328115691,
            235.38790178262499861,
            1519.377599407554805,
            6485.558298266760755,
            18615.571640885098091,
            34900.952721145977266,
            38912.003286093271411,
            19685.429676859990727
            ];
            var p = [
            0.21589853405795699,
            0.1274011611602473639,
            0.022235277870649807,
            0.001421619193227893466,
            2.9112874951168792e-5,
            0.02307344176494017303
            ];
            var q = [
            1.28426009614491121,
            0.468238212480865118,
            0.0659881378689285515,
            0.00378239633202758244,
            7.29751555083966205e-5
            ];

            var xden, xnum, temp, del, eps, xsq, y, i, lower, upper;

            /* Consider changing these : */
            eps = jstat.DBL_EPSILON * 0.5;

            /* i_tail in {0,1,2} =^= {lower, upper, both} */
            lower = i_tail != 1;
            upper = i_tail != 0;

            y = Math.abs(x);

            if (y <= 0.67448975) { /* qnorm(3/4) = .6744.... -- earlier had 0.66291 */
                if (y > eps) {
                    xsq = x * x;
                    xnum = a[4] * xsq;
                    xden = xsq;
                    for (i = 0; i < 3; ++i) {
                        xnum = (xnum + a[i]) * xsq;
                        xden = (xden + b[i]) * xsq;
                    }
                } else {
                    xnum = xden = 0.0;
                }
                temp = x * (xnum + a[3]) / (xden + b[3]);
                if(lower)  cum = 0.5 + temp;
                if(upper) ccum = 0.5 - temp;
                if(log_p) {
                    if(lower)  cum = Math.log(cum);
                    if(upper) ccum = Math.log(ccum);
                }

            } else if (y <= jstat.SQRT_32) {
                /* Evaluate pnorm for 0.674.. = qnorm(3/4) < |x| <= sqrt(32) ~= 5.657 */

                xnum = c[8] * y;
                xden = y;
                for (i = 0; i < 7; ++i) {
                    xnum = (xnum + c[i]) * y;
                    xden = (xden + d[i]) * y;
                }
                temp = (xnum + c[7]) / (xden + d[7]);

                /* do_del */
                xsq = jstat.trunc(x * 16) / 16;
                del = (x - xsq) * (x + xsq);
                if(log_p) {
                    cum = (-xsq * xsq * 0.5) + (-del * 0.5) + Math.log(temp);
                    if((lower && x > 0.) || (upper && x <= 0.))
                        ccum = jstat.log1p(-Math.exp(-xsq * xsq * 0.5) *
                            Math.exp(-del * 0.5) * temp);
                }
                else {
                    cum = Math.exp(-xsq * xsq * 0.5) * Math.exp(-del * 0.5) * temp;
                    ccum = 1.0 - cum;
                }
                /* end do_del */

                /* swap_tail */
                if (x > 0.0) {/* swap  ccum <--> cum */
                    temp = cum;
                    if(lower) {
                        cum = ccum;

                    }
                    ccum = temp;
                }
            /* end swap_tail */

            }
            /* else	  |x| > sqrt(32) = 5.657 :
             * the next two case differentiations were really for lower=T, log=F
             * Particularly	 *not*	for  log_p !

             * Cody had (-37.5193 < x  &&  x < 8.2924) ; R originally had y < 50
             *
             * Note that we do want symmetry(0), lower/upper -> hence use y
             */

            else if((log_p && y < 1e170)|| (lower && -37.5193 < x  &&  x < 8.2924)
                || (upper && -8.2924  < x  &&  x < 37.5193)) {
                /* Evaluate pnorm for x in (-37.5, -5.657) union (5.657, 37.5) */
                xsq = 1.0 / (x * x); /* (1./x)*(1./x) might be better */
                xnum = p[5] * xsq;
                xden = xsq;
                for (i = 0; i < 4; ++i) {
                    xnum = (xnum + p[i]) * xsq;
                    xden = (xden + q[i]) * xsq;
                }
                temp = xsq * (xnum + p[4]) / (xden + q[4]);
                temp = (jstat.ONE_SQRT_2PI - temp) / y;

                /* do_del */
                xsq = jstat.trunc(x * 16) / 16;
                del = (x - xsq) * (x + xsq);
                if(log_p) {
                    cum = (-xsq * xsq * 0.5) + (-del * 0.5) + Math.log(temp);
                    if((lower && x > 0.) || (upper && x <= 0.))
                        ccum = jstat.log1p(-Math.exp(-xsq * xsq * 0.5) *
                            Math.exp(-del * 0.5) * temp);
                }
                else {
                    cum = Math.exp(-xsq * xsq * 0.5) * Math.exp(-del * 0.5) * temp;
                    ccum = 1.0 - cum;
                }
                /* end do_del */

                /* swap_tail */
                if (x > 0.0) {/* swap  ccum <--> cum */
                    temp = cum;
                    if(lower) {
                        cum = ccum;

                    }
                    ccum = temp;
                }
            /* end swap_tail */

            } else { /* large x such that probs are 0 or 1 */
                if(x > 0) {
                    cum = (log_p) ? 0.0 : 1.0;  // R_D__1
                    ccum = (log_p) ? Number.NEGATIVE_INFINITY : 0.0;  //R_D__0;
                } else {
                    cum = (log_p) ? Number.NEGATIVE_INFINITY : 0.0;  //R_D__0;
                    ccum = (log_p) ? 0.0 : 1.0;  // R_D__1
                }
            }

            return [cum, ccum];
        }

        var p, cp;
        var mu = this._mean;
        var sigma = this._sigma;
        var R_DT_0, R_DT_1;

        if(lower_tail) {
            if(log_p) {
                R_DT_0 = Number.NEGATIVE_INFINITY;
                R_DT_1 = 0.0;
            } else {
                R_DT_0 = 0.0;
                R_DT_1 = 1.0;
            }
        } else {
            if(log_p) {
                R_DT_0 = 0.0;
                R_DT_1 = Number.NEGATIVE_INFINITY;
            } else {
                R_DT_0 = 1.0;
                R_DT_1 = 0.0;
            }
        }

        if(!jstat.isFinite(x) && mu == x) return Number.NaN;
        if(sigma <= 0) {
            if(sigma < 0) {
                console.warn("Sigma is less than 0");
                return Number.NaN;
            }
            return (x < mu) ? R_DT_0 : R_DT_1;
        }

        p = (x - mu) / sigma;

        if(!jstat.isFinite(p)) {
            return (x < mu) ? R_DT_0 : R_DT_1;
        }

        x = p;

        // pnorm_both(x, &p, &cp, (lower_tail ? 0 : 1), log_p);
        // result[0] == &p
        // result[1] == &cp

        var result = pnorm_both(x, p, cp, (lower_tail ? false : true), log_p);

        return (lower_tail ? result[0] : result[1]);

    },
    getMean: function() {
        return this._mean;
    },
    getSigma: function() {
        return this._sigma;
    },
    getVariance: function() {
        return this._sigma*this._sigma;
    }
});

/**
 *  A Log-normal distribution object
 */
var LogNormalDistribution = ContinuousDistribution.extend({
    init: function(location, scale) {
        this._super('LogNormal')
        this._location = parseFloat(location);
        this._scale = parseFloat(scale);
        this._string = "LogNormal ("+this._location.toFixed(2)+", " + this._scale.toFixed(2) + ")";
    },
    _pdf: function(x, give_log) {
        var y;
        var sdlog = this._scale;
        var meanlog = this._location;
        if(give_log == null) {
            give_log = false;
        }

        if(sdlog <= 0) throw "Illegal parameter in _pdf";

        if(x <= 0) {
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
        }

        y = (Math.log(x) - meanlog) / sdlog;

        return (give_log ? -(jstat.LN_SQRT_2PI + 0.5 * y * y + Math.log(x * sdlog)) :
            jstat.ONE_SQRT_2PI * Math.exp(-0.5 * y * y) / (x * sdlog));

    },
    _cdf: function(x, lower_tail, log_p) {
        var sdlog = this._scale;
        var meanlog = this._location;
        if(lower_tail == null) {
            lower_tail = true;
        }
        if(log_p == null) {
            log_p = false;
        }


        if(sdlog <= 0) {
            throw "illegal std in _cdf";
        }

        if(x > 0) {
            var nd = new NormalDistribution(meanlog, sdlog);
            return nd._cdf(Math.log(x), lower_tail, log_p);
        }
        if(lower_tail) {
            return (log_p) ? Number.NEGATIVE_INFINITY : 0.0;    // R_D__0
        } else {
            return (log_p) ? 0.0 : 1.0;                         // R_D__1
        }
    },
    getLocation: function() {
        return this._location;
    },
    getScale: function() {
        return this._scale;
    },
    getMean: function() {
        return Math.exp((this._location + this._scale) /  2);
    },
    getVariance: function() {
        var ans = (Math.exp(this._scale)-1)*Math.exp(2*this._location+this._scale);
        return ans;
    }
});


/**
 *  Gamma distribution object
 */
var GammaDistribution = ContinuousDistribution.extend({
    init: function(shape, scale) {
        this._super('Gamma');
        this._shape = parseFloat(shape);
        this._scale = parseFloat(scale);
        this._string = "Gamma ("+this._shape.toFixed(2)+", " + this._scale.toFixed(2) + ")";
    },
    _pdf: function(x, give_log) {
        var pr;
        var shape = this._shape;
        var scale = this._scale;
        if(give_log == null) {
            give_log = false;    // default value
        }

        if(shape < 0 || scale <= 0) {
            throw "Illegal argument in _pdf";
        }

        if(x < 0) {
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; // R_D__0
        }
        if(shape == 0) { /* point mass at 0 */
            return (x == 0) ? Number.POSITIVE_INFINITY : (give_log) ? Number.NEGATIVE_INFINITY : 0.0;   // R_D__0
        }
        if(x == 0) {
            if(shape < 1) return Number.POSITIVE_INFINITY;
            if(shape > 1) return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; // R_D__0
            /* else */
            return (give_log) ? -Math.log(scale) : 1/scale;
        }

        if(shape < 1) {
            pr = jstat.dopois_raw(shape, x/scale, give_log);
            return give_log ? pr + Math.log(shape/x) : pr*shape/x;
        }
        /* else shape >= 1 */
        pr = jstat.dopois_raw(shape-1, x/scale, give_log);
        return give_log ? pr - Math.log(scale) : pr/scale;

    },
    /**
     *	This function computes the distribution function for the
     *	gamma distribution with shape parameter alph and scale parameter
     *	scale.	This is also known as the incomplete gamma function.
     *	See Abramowitz and Stegun (6.5.1) for example.
     */
    _cdf: function(x, lower_tail, log_p) {
        /* define USE_PNORM */
        function USE_PNORM() {
            pn1 = Math.sqrt(alph) * 3.0 * (Math.pow(x/alph,1.0/3.0) + 1.0 / (9.0 * alph) - 1.0);
            var norm_dist = new NormalDistribution(0.0, 1.0);
            return norm_dist._cdf(pn1, lower_tail, log_p);
        }

        /* Defaults */
        if(lower_tail == null) lower_tail = true;
        if(log_p == null) log_p = false;
        var alph = this._shape;
        var scale = this._scale;
        var xbig = 1.0e+8;
        var xlarge = 1.0e+37;
        var alphlimit = 1e5;
        var pn1,pn2,pn3,pn4,pn5,pn6,arg,a,b,c,an,osum,sum,n,pearson;

        if(alph <= 0. || scale <= 0.) {
            console.warn('Invalid gamma params in _cdf');
            return Number.NaN;
        }

        x/=scale;
        if(isNaN(x)) return x;
        if(x <= 0.0) {
            // R_DT_0
            if(lower_tail) {
                // R_D__0
                return (log_p) ? Number.NEGATIVE_INFINITY : 0.0;
            } else {
                // R_D__1
                return (log_p) ? 0.0 : 1.0;
            }
        }

        if(alph > alphlimit) {
            return USE_PNORM();
        }

        if(x > xbig * alph) {
            if(x > jstat.DBL_MAX * alph) {
                // R_DT_1
                if(lower_tail) {
                    // R_D__1
                    return (log_p) ? 0.0 : 1.0;
                } else {
                    // R_D__0
                    return (log_p) ? Number.NEGATIVE_INFINITY : 0.0;
                }
            } else {
                return USE_PNORM();
            }
        }

        if(x <= 1.0 || x < alph) {
            pearson = 1; /* use pearson's series expansion */
            arg = alph * Math.log(x) - x - jstat.lgamma(alph + 1.0);

            c = 1.0;
            sum = 1.0;
            a = alph;
            do {
                a += 1.0;
                c *= x / a;
                sum += c;
            } while(c > jstat.DBL_EPSILON * sum);
        } else { /* x >= max( 1, alph) */
            pearson = 0;/* use a continued fraction expansion */
            arg = alph * Math.log(x) - x - jstat.lgamma(alph);

            a = 1. - alph;
            b = a + x + 1.;
            pn1 = 1.;
            pn2 = x;
            pn3 = x + 1.;
            pn4 = x * b;
            sum = pn3 / pn4;

            for (n = 1; ; n++) {
                a += 1.;/* =   n+1 -alph */
                b += 2.;/* = 2(n+1)-alph+x */
                an = a * n;
                pn5 = b * pn3 - an * pn1;
                pn6 = b * pn4 - an * pn2;
                if (Math.abs(pn6) > 0.) {
                    osum = sum;
                    sum = pn5 / pn6;
                    if (Math.abs(osum - sum) <= jstat.DBL_EPSILON * jstat.fmin2(1.0, sum))
                        break;
                }
                pn1 = pn3;
                pn2 = pn4;
                pn3 = pn5;
                pn4 = pn6;

                if (Math.abs(pn5) >= xlarge) {
                    pn1 /= xlarge;
                    pn2 /= xlarge;
                    pn3 /= xlarge;
                    pn4 /= xlarge;
                }
            }
        }
        arg += Math.log(sum);
        lower_tail = (lower_tail == pearson);

        if (log_p && lower_tail)
            return(arg);
        /* else */
        /* sum = exp(arg); and return   if(lower_tail) sum	else 1-sum : */

        if(lower_tail) {
            return Math.exp(arg);
        } else {
            if(log_p) {
                // R_Log1_Exp(arg);
                return (arg > -Math.LN2)  ? Math.log(-jstat.expm1(arg)) : jstat.log1p(-Math.exp(arg));
            } else {
                return -jstat.expm1(arg);
            }
        }
    },
    getShape: function() {
        return this._shape;
    },
    getScale: function() {
        return this._scale;
    },
    getMean: function() {
        return this._shape * this._scale;
    },
    getVariance: function() {
        return this._shape*Math.pow(this._scale,2);
    }
});

/**
 *  A Beta distribution object
 */
var BetaDistribution = ContinuousDistribution.extend({
    init: function(alpha, beta) {
        this._super('Beta');
        this._alpha = parseFloat(alpha);
        this._beta = parseFloat(beta);
        this._string = "Beta ("+this._alpha.toFixed(2)+", " + this._beta.toFixed(2) + ")";
    },
    _pdf: function(x, give_log) {
        if(give_log == null) give_log = false; // default;
        var a = this._alpha;
        var b = this._beta;
        var lval;
        if(a <= 0 || b <= 0) {
            console.warn('Illegal arguments in _pdf');
            return Number.NaN;
        }
        if(x < 0 || x > 1) {
            // R_D__0
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
        }
        if(x == 0) {
            if(a > 1) {
                // R_D__0
                return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
            }
            if(a < 1) {
                return Number.POSITIVE_INFINITY;
            }
            /*a == 1 */ return (give_log) ? Math.log(b) : b;    // R_D_val(b)
        }
        if(x == 1) {
            if(b > 1) {
                // R_D__0
                return (give_log) ? Number.NEGATIVE_INFINITY : 0.0;
            }
            if(b < 1) {
                return Number.POSITIVE_INFINITY;
            }
            /* b == 1 */ return (give_log) ? Math.log(a) : a;   // R_D_val(a)
        }
        if(a<=2||b<=2) {
            lval = (a-1)*Math.log(x) + (b-1)*jstat.log1p(-x) - jstat.logBeta(a, b);
        } else {
            lval = Math.log(a+b-1) + jstat.dbinom_raw(a-1, a+b-2, x, 1-x, true);
        }
        //R_D_exp(lval)
        return (give_log) ? lval : Math.exp(lval);
    },
    _cdf: function(x, lower_tail, log_p) {
        if(lower_tail == null) lower_tail = true;
        if(log_p == null) log_p = false;

        var pin = this._alpha;
        var qin = this._beta;

        if(pin <= 0 || qin <= 0) {
            console.warn('Invalid argument in _cdf');
            return Number.NaN;
        }

        if(x <= 0) {
            //R_DT_0;
            if(lower_tail) {
                // R_D__0
                return (log_p) ? Number.NEGATIVE_INFINITY : 0.0;
            } else {
                // R_D__1
                return (log_p) ? 0.1 : 1.0;
            }
        }

        if(x >= 1){
            // R_DT_1
            if(lower_tail) {
                // R_D__1
                return (log_p) ? 0.1 : 1.0;
            } else {
                // R_D__0
                return (log_p) ? Number.NEGATIVE_INFINITY : 0.0;
            }
        }

        /* else */
        return jstat.incompleteBeta(pin, qin, x);
    },
    getAlpha: function() {
        return this._alpha;
    },
    getBeta: function() {
        return this._beta;
    },
    getMean: function() {
        return this._alpha / (this._alpha+ this._beta);
    },
    getVariance: function() {
        var ans = (this._alpha * this._beta) / (Math.pow(this._alpha+this._beta,2)*(this._alpha+this._beta+1));
        return ans;
    }
});

var StudentTDistribution = ContinuousDistribution.extend({
    init: function(degreesOfFreedom, mu) {
        this._super('StudentT');
        this._dof = parseFloat(degreesOfFreedom);

        if(mu != null) {
            this._mu = parseFloat(mu);
            this._string = "StudentT ("+this._dof.toFixed(2)+", " + this._mu.toFixed(2)+ ")";
        } else {
            this._mu = 0.0;
            this._string = "StudentT ("+this._dof.toFixed(2)+")";
        }

    },
    _pdf: function(x, give_log) {
        if(give_log == null) give_log = false;
        if(this._mu == null) {
            return this._dt(x, give_log);
        } else {
            var y = this._dnt(x, give_log);
            if(y > 1){
                console.warn('x:' + x + ', y: ' + y);
            }
            return y;
        }
    },
    _cdf: function(x, lower_tail, give_log) {
        if(lower_tail == null) lower_tail = true;
        if(give_log == null) give_log = false;
        if(this._mu == null) {
            return this._pt(x, lower_tail, give_log);
        } else {
            return this._pnt(x, lower_tail, give_log);
        }
    },
    _dt: function(x, give_log) {
        var t,u;
        var n = this._dof;
        if (n <= 0){
            console.warn('Invalid parameters in _dt');
            return Number.NaN;
        }
        if(!jstat.isFinite(x)) {
            return (give_log) ? Number.NEGATIVE_INFINITY : 0.0; // R_D__0;
        }

        if(!jstat.isFinite(n)) {
            var norm = new NormalDistribution(0.0, 1.0);
            return norm.density(x, give_log);
        }


        t = -jstat.bd0(n/2.0,(n+1)/2.0) + jstat.stirlerr((n+1)/2.0) - jstat.stirlerr(n/2.0);
        if ( x*x > 0.2*n )
            u = Math.log( 1+ x*x/n ) * n/2;
        else
            u = -jstat.bd0(n/2.0,(n+x*x)/2.0) + x*x/2.0;

        var p1 = jstat.TWO_PI *(1+x*x/n);
        var p2 = t-u;

        return (give_log) ? -0.5*Math.log(p1) + p2 : Math.exp(p2)/Math.sqrt(p1);       // R_D_fexp(M_2PI*(1+x*x/n), t-u);
    },
    _dnt: function(x, give_log) {
        if(give_log == null) give_log = false;
        var df = this._dof;
        var ncp = this._mu;
        var u;

        if(df <= 0.0) {
            console.warn("Illegal arguments _dnf");
            return Number.NaN;
        }
        if(ncp == 0.0) {
            return this._dt(x, give_log);
        }

        if(!jstat.isFinite(x)) {
            // R_D__0
            if(give_log) {
                return Number.NEGATIVE_INFINITY;
            } else {
                return 0.0;
            }
        }

        /* If infinite df then the density is identical to a
           normal distribution with mean = ncp.  However, the formula
           loses a lot of accuracy around df=1e9
         */
        if(!isFinite(df) || df > 1e8) {
            var dist = new NormalDistribution(ncp, 1.);
            return dist.density(x, give_log);
        }

        /* Do calculations on log scale to stabilize */

        /* Consider two cases: x ~= 0 or not */
        if (Math.abs(x) > Math.sqrt(df * jstat.DBL_EPSILON)) {
            var newT = new StudentTDistribution(df+2, ncp);
            u = Math.log(df) - Math.log(Math.abs(x)) +
            Math.log(Math.abs(newT._pnt(x*Math.sqrt((df+2)/df), true, false) -
                this._pnt(x, true, false)));
        /* FIXME: the above still suffers from cancellation (but not horribly) */
        }
        else {  /* x ~= 0 : -> same value as for  x = 0 */
            u = jstat.lgamma((df+1)/2) - jstat.lgamma(df/2)
            - .5*(Math.log(Math.PI) + Math.log(df) + ncp*ncp);
        }

        return (give_log ? u : Math.exp(u));
    },
    _pt: function(x, lower_tail, log_p) {
        if(lower_tail == null) lower_tail = true;
        if(log_p == null) log_p = false;
        var val, nx;
        var n = this._dof;
        var DT_0, DT_1;

        if(lower_tail) {
            if(log_p) {
                DT_0 = Number.NEGATIVE_INFINITY;
                DT_1 = 1.;
            } else {
                DT_0 = 0.;
                DT_1 = 1.;
            }
        } else {
            if(log_p) {
                // not lower_tail but log_p
                DT_0 = 0.;
                DT_1 = Number.NEGATIVE_INFINITY;
            } else {
                // not lower_tail and not log_p
                DT_0 = 1.;
                DT_1 = 0.;
            }
        }

        if(n <= 0.0) {
            console.warn("Invalid T distribution _pt");
            return Number.NaN;
        }
        var norm = new NormalDistribution(0,1);
        if(!jstat.isFinite(x)) {
            return (x < 0) ? DT_0 : DT_1;
        }
        if(!jstat.isFinite(n)) {
            return norm._cdf(x, lower_tail, log_p);
        }

        if (n > 4e5) { /*-- Fixme(?): test should depend on `n' AND `x' ! */
            /* Approx. from	 Abramowitz & Stegun 26.7.8 (p.949) */
            val = 1./(4.*n);
            return norm._cdf(x*(1. - val)/sqrt(1. + x*x*2.*val), lower_tail, log_p);
        }

        nx = 1 + (x/n)*x;
        /* FIXME: This test is probably losing rather than gaining precision,
         * now that pbeta(*, log_p = TRUE) is much better.
         * Note however that a version of this test *is* needed for x*x > D_MAX */
        if(nx > 1e100) { /* <==>  x*x > 1e100 * n  */
            /* Danger of underflow. So use Abramowitz & Stegun 26.5.4
	   pbeta(z, a, b) ~ z^a(1-z)^b / aB(a,b) ~ z^a / aB(a,b),
	   with z = 1/nx,  a = n/2,  b= 1/2 :
             */
            var lval;
            lval = -0.5*n*(2*Math.log(Math.abs(x)) - Math.log(n))
            - jstat.logBeta(0.5*n, 0.5) - Math.log(0.5*n);
            val = log_p ? lval : Math.exp(lval);
        } else {
            /*
            val = (n > x * x)
            //    ? pbeta (x * x / (n + x * x), 0.5, n / 2., 0, log_p)
           // : pbeta (1. / nx,             n / 2., 0.5, 1, log_p);
             */
            if(n > x * x) {
                var beta = new BetaDistribution(0.5, n/2.);
                return beta._cdf(x*x/ (n + x * x), false, log_p);
            } else {
                beta = new BetaDistribution(n / 2., 0.5);
                return beta._cdf(1. / nx, true, log_p);
            }


        }

        /* Use "1 - v"  if	lower_tail  and	 x > 0 (but not both):*/
        if(x <= 0.)
            lower_tail = !lower_tail;

        if(log_p) {
            if(lower_tail) return jstat.log1p(-0.5*Math.exp(val));
            else return val - M_LN2; /* = log(.5* pbeta(....)) */
        }
        else {
            val /= 2.;
            if(lower_tail) {
                return (0.5 - val + 0.5);
            } else {
                return val;
            }
        }
    },
    _pnt: function(t, lower_tail, log_p) {

        var dof = this._dof;
        var ncp = this._mu;
        var DT_0, DT_1;

        if(lower_tail) {
            if(log_p) {
                DT_0 = Number.NEGATIVE_INFINITY;
                DT_1 = 1.;
            } else {
                DT_0 = 0.;
                DT_1 = 1.;
            }
        } else {
            if(log_p) {
                // not lower_tail but log_p
                DT_0 = 0.;
                DT_1 = Number.NEGATIVE_INFINITY;
            } else {
                // not lower_tail and not log_p
                DT_0 = 1.;
                DT_1 = 0.;
            }
        }

        var albeta, a, b, del, errbd, lambda, rxb, tt, x;
        var geven, godd, p, q, s, tnc, xeven, xodd;
        var it, negdel;

        /* note - itrmax and errmax may be changed to suit one's needs. */
        var ITRMAX = 1000;
        var ERRMAX = 1.e-7;

        if(dof <= 0.0) {
            return Number.NaN;
        } else if (dof == 0.0) {
            return this._pt(t);
        }

        if(!jstat.isFinite(t)) {
            return (t < 0) ? DT_0 : DT_1;
        }
        if(t >= 0.) {
            negdel = false;
            tt = t;
            del = ncp;
        } else {
            /* We deal quickly with left tail if extreme,
	   since pt(q, df, ncp) <= pt(0, df, ncp) = \Phi(-ncp) */
            if(ncp >= 40 && (!log_p || !lower_tail)) {
                return DT_0;
            }
            negdel = true;
            tt = -t;
            del = -ncp;
        }

        if(dof > 4e5 || del*del > 2* Math.LN2 * (-(jstat.DBL_MIN_EXP))) {
            /*-- 2nd part: if del > 37.62, then p=0 below
	    FIXME: test should depend on `df', `tt' AND `del' ! */
            /* Approx. from	 Abramowitz & Stegun 26.7.10 (p.949) */
            s=1./(4.*dof);
            var norm = new NormalDistribution(del, Math.sqrt(1. + tt*tt*2.*s));
            var result = norm._cdf(tt*(1.-s), lower_tail != negdel, log_p);
            return result;
        }

        /* initialize twin series */
        /* Guenther, J. (1978). Statist. Computn. Simuln. vol.6, 199. */
        x = t * t;
        rxb = dof/(x + dof);/* := (1 - x) {x below} -- but more accurately */
        x = x / (x + dof);/* in [0,1) */
        if (x > 0.) {/* <==>  t != 0 */
            lambda = del * del;
            p = .5 * Math.exp(-.5 * lambda);
            if(p == 0.) {   // underflow!
                console.warn("underflow in _pnt");
                return DT_0;
            }
            q = jstat.SQRT_2dPI * p * del;
            s = .5 - p;
            if(s < 1e-7) {
                s = -0.5 * jstat.expm1(-0.5 * lambda);
            }
            a = .5;
            b = .5 * dof;
            /* rxb = (1 - x) ^ b   [ ~= 1 - b*x for tiny x --> see 'xeven' below]
             * where '(1 - x)' =: rxb {accurately!} above */
            rxb = Math.pow(rxb, b);
            albeta = jstat.LN_SQRT_PI + jstat.lgamma(b) - jstat.lgamma(.5 + b);
            /* TODO: change incompleteBeta function to accept lower_tail and p_log */
            xodd = jstat.incompleteBeta(a, b, x);
            godd = 2. * rxb * Math.exp(a * Math.log(x) - albeta);
            tnc = b * x;
            xeven = (tnc < jstat.DBL_EPSILON) ? tnc : 1. - rxb;
            geven = tnc * rxb;
            tnc = p * xodd + q * xeven;

            /* repeat until convergence or iteration limit */
            for(it = 1; it <= ITRMAX; it++) {
                a += 1.;
                xodd  -= godd;
                xeven -= geven;
                godd  *= x * (a + b - 1.) / a;
                geven *= x * (a + b - .5) / (a + .5);
                p *= lambda / (2 * it);
                q *= lambda / (2 * it + 1);
                tnc += p * xodd + q * xeven;
                s -= p;
                /* R 2.4.0 added test for rounding error here. */
                if(s < -1.e-10) { /* happens e.g. for (t,df,ncp)=(40,10,38.5), after 799 it.*/
                    //console.write("precision error _pnt");
                    break;
                }
                if(s <= 0 && it > 1) break;

                errbd = 2. * s * (xodd - godd);

                if(Math.abs(errbd) < ERRMAX) break;/*convergence*/
            }

            if(it == ITRMAX) {
                throw "Non-convergence _pnt";
            }
        } else {
            tnc = 0.;
        }
        norm = new NormalDistribution(0,1);
        tnc += norm._cdf(-del, /*lower*/true, /*log_p*/ false);

        lower_tail = lower_tail != negdel; /* xor */
        if(tnc > 1 - 1e-10 && lower_tail) {
            //console.warn("precision error _pnt");
        }
        var res = jstat.fmin2(tnc, 1.);
        if(lower_tail) {
            if(log_p) {
                return Math.log(res);
            } else {
                return res;
            }
        } else {
            if(log_p) {
                return jstat.log1p(-(res));
            } else {
                return (0.5 - (res) + 0.5);
            }
        }
    },
    getDegreesOfFreedom: function() {
        return this._dof;
    },
    getNonCentralityParameter: function() {
        return this._mu;
    },
    getMean: function() {
        if(this._dof > 1) {
            var ans = (1/2)*Math.log(this._dof/2) + jstat.lgamma((this._dof-1)/2) - jstat.lgamma(this._dof/2)
            return Math.exp(ans) * this._mu;
        } else {
            return Number.NaN;
        }
    },
    getVariance: function() {
        if(this._dof > 2) {
            var ans = this._dof * (1 + this._mu*this._mu)/(this._dof-2) - (((this._mu*this._mu * this._dof) / 2) * Math.pow(Math.exp(jstat.lgamma((this._dof - 1)/2)-jstat.lgamma(this._dof/2)), 2));
            return ans;
        } else {
            return Number.NaN;
        }
    }
});


/******************************************************************************/
/*                      jQuery Flot graph objects                             */
/******************************************************************************/

var Plot = Class.extend({
    init: function(id, options) {
        this._container = '#' + String(id);
        this._plots = [];
        this._flotObj = null;
        this._locked = false;

        if(options != null) {
            this._options = options;
        } else {
            this._options = {
            };
        }

    },
    getContainer: function() {
        return this._container;
    },
    getGraph: function() {
        return this._flotObj;
    },
    setData: function(data) {
        this._plots = data;
    },
    clear: function() {
        this._plots = [];
    //this.render();
    },
    showLegend: function() {
        this._options.legend = {
            show: true
        }
        this.render();
    },
    hideLegend: function() {
        this._options.legend = {
            show: false
        }
        this.render();
    },
    render: function() {
        this._flotObj = null;
        this._flotObj = $.plot($(this._container), this._plots, this._options);
    }
});

var DistributionPlot = Plot.extend({
    init: function(id, distribution, range, options) {
        this._super(id, options);
        this._showPDF = true;
        this._showCDF = false;
        this._pdfValues = [];     // raw values for pdf
        this._cdfValues = [];     // raw values for cdf
        this._maxY = 1;
        this._plotType = 'line';    // line, point, both
        this._fill = false;
        this._distribution = distribution;    // underlying PDF
        // Range object for the plot
        if(range != null && Range.validate(range)) {
            this._range = range;
        } else {
            this._range = this._distribution.getRange(); // no range supplied, use distribution default
        }

        // render
        if(this._distribution != null) {
            this._maxY = this._generateValues();   // create the pdf/cdf values in the ctor
        } else {
            this._options.xaxis = {
                min: range.getMinimum(),
                max: range.getMaximum()
            }
            this._options.yaxis = {
                max: 1
            }
        }



        this.render();
    },
    setHover: function(bool) {
        if(bool) {
            if(this._options.grid == null) {
                this._options.grid = {
                    hoverable: true,
                    mouseActiveRadius: 25
                }
            } else {
                this._options.grid.hoverable = true,
                this._options.grid.mouseActiveRadius = 25
            }
            function showTooltip(x, y, contents, color) {
                $('<div id="jstat_tooltip">' + contents + '</div>').css( {
                    position: 'absolute',
                    display: 'none',
                    top: y + 15,
                    'font-size': 'small',
                    left: x + 5,
                    border: '1px solid ' + color[1],
                    color: color[2],
                    padding: '5px',
                    'background-color': color[0],
                    opacity: 0.80
                }).appendTo("body").show();
            }
            var previousPoint = null;
            $(this._container).bind("plothover", function(event, pos, item) {
                $("#x").text(pos.x.toFixed(2));
                $("#y").text(pos.y.toFixed(2));
                if (item) {
                    if (previousPoint != item.datapoint) {
                        previousPoint = item.datapoint;
                        $("#jstat_tooltip").remove();
                        var x = jstat.toSigFig(item.datapoint[0],2), y = jstat.toSigFig(item.datapoint[1], 2);
                        var text = null;
                        var color = item.series.color;
                        if(item.series.label == 'PDF') {
                            text = "P(" + x + ") = " + y;
                            color = ["#fee", "#fdd", "#C05F5F"];
                        } else {
                            // cdf
                            text = "F(" + x + ") = " + y;
                            color = ["#eef", "#ddf", "#4A4AC0"];
                        }
                        showTooltip(item.pageX, item.pageY, text, color);
                    }
                }
                else {
                    $("#jstat_tooltip").remove();
                    previousPoint = null;
                }
            });
            $(this._container).bind("mouseleave", function() {
                if($('#jstat_tooltip').is(':visible')) {
                    $('#jstat_tooltip').remove();
                    previousPoint = null;
                }
            });
        } else {
            // unbind
            if(this._options.grid == null) {
                this._options.grid = {
                    hoverable: false
                }
            } else {
                this._options.grid.hoverable = false
            }
            $(this._container).unbind("plothover");
        }

        this.render();
    },
    setType: function(type) {
        this._plotType = type;
        var lines = {};
        var points = {};
        if(this._plotType == 'line') {
            lines.show = true;
            points.show = false;
        } else if(this._plotType == 'points') {
            lines.show = false;
            points.show = true;
        } else if(this._plotType == 'both') {
            lines.show = true;
            points.show = true;
        }
        if(this._options.series == null) {
            this._options.series = {
                lines: lines,
                points: points
            }
        } else {
            if(this._options.series.lines == null) {
                this._options.series.lines = lines;
            } else {
                this._options.series.lines.show = lines.show;
            }
            if(this._options.series.points == null) {
                this._options.series.points = points;
            } else {
                this._options.series.points.show = points.show;
            }
        }

        this.render();
    },
    setFill: function(bool) {
        this._fill = bool;
        if(this._options.series == null) {
            this._options.series = {
                lines: {
                    fill: bool
                }
            }
        } else {
            if(this._options.series.lines == null) {
                this._options.series.lines = {
                    fill: bool
                }
            } else {
                this._options.series.lines.fill = bool;
            }
        }
        this.render();
    },
    clear: function() {
        this._super();
        this._distribution = null;
        this._pdfValues = [];
        this._cdfValues = [];
        this.render();
    },
    _generateValues: function() {
        this._cdfValues = [];     // reinitialize the arrays.
        this._pdfValues = [];

        var xs = this._range.getPoints();

        this._options.xaxis = {
            min: xs[0],
            max: xs[xs.length-1]
        }
        var pdfs = this._distribution.density(this._range);
        var cdfs = this._distribution.cumulativeDensity(this._range);
        for(var i = 0; i < xs.length; i++) {
            if(pdfs[i] == Number.POSITIVE_INFINITY || pdfs[i] == Number.NEGATIVE_INFINITY) {
                pdfs[i] = null;
            }
            if(cdfs[i] == Number.POSITIVE_INFINITY || cdfs[i] == Number.NEGATIVE_INFINITY) {
                cdfs[i] = null;
            }
            this._pdfValues.push([xs[i], pdfs[i]]);
            this._cdfValues.push([xs[i], cdfs[i]]);
        }
        return jstat.max(pdfs);
    },
    showPDF: function() {
        this._showPDF = true;
        this.render();
    },
    hidePDF: function() {
        this._showPDF = false;
        this.render();
    },
    showCDF: function() {
        this._showCDF = true;
        this.render();
    },
    hideCDF: function() {
        this._showCDF = false;
        this.render();
    },
    setDistribution: function(distribution, range) {
        this._distribution = distribution;
        if(range != null) {
            this._range = range;
        } else {
            this._range = distribution.getRange();
        }
        this._maxY = this._generateValues();
        this._options.yaxis = {
            max: this._maxY*1.1
        }

        this.render();
    },
    getDistribution: function() {
        return this._distribution;
    },
    getRange: function() {
        return this._range;
    },
    setRange: function(range) {
        this._range = range;
        this._generateValues();
        this.render();
    },
    render: function() {
        if(this._distribution != null) {
            if(this._showPDF && this._showCDF) {
                this.setData([{
                    yaxis: 1,
                    data:this._pdfValues,
                    color: 'rgb(237,194,64)',
                    clickable: false,
                    hoverable: true,
                    label: "PDF"
                }, {
                    yaxis: 2,
                    data:this._cdfValues,
                    clickable: false,
                    color: 'rgb(175,216,248)',
                    hoverable: true,
                    label: "CDF"
                }]);
                this._options.yaxis = {
                    max: this._maxY*1.1
                }
            } else if(this._showPDF) {
                this.setData([{
                    data:this._pdfValues,
                    hoverable: true,
                    color: 'rgb(237,194,64)',
                    clickable: false,
                    label: "PDF"
                }]);
                this._options.yaxis = {
                    max: this._maxY*1.1
                }
            } else if(this._showCDF) {
                this.setData([{
                    data:this._cdfValues,
                    hoverable: true,
                    color: 'rgb(175,216,248)',
                    clickable: false,
                    label: "CDF"
                }]);
                this._options.yaxis = {
                    max: 1.1
                }
            }
        } else {
            this.setData([]);
        }
        this._super();  // Call the parent plot method
    }
});

var DistributionFactory = {};
DistributionFactory.build = function(json) {
    /*
    if(json.name == null) {
        try{
            json = JSON.parse(json);
        }
        catch(err) {
            throw "invalid JSON";
        }

    // try to parse it
    }*/

    /*
    if(json.name != null) {
        var name = json.name;
    } else {
        throw "Malformed JSON provided to DistributionBuilder " + json;
    }
     */
    if(json.NormalDistribution) {
        if(json.NormalDistribution.mean != null && json.NormalDistribution.standardDeviation != null) {
            return new NormalDistribution(json.NormalDistribution.mean[0], json.NormalDistribution.standardDeviation[0]);
        } else {
            throw "Malformed JSON provided to DistributionBuilder " + json;
        }
    } else if (json.LogNormalDistribution) {
        if(json.LogNormalDistribution.location != null && json.LogNormalDistribution.scale != null) {
            return new LogNormalDistribution(json.LogNormalDistribution.location[0], json.LogNormalDistribution.scale[0]);
        } else {
            throw "Malformed JSON provided to DistributionBuilder " + json;
        }
    } else if (json.BetaDistribution) {
        if(json.BetaDistribution.alpha != null && json.BetaDistribution.beta != null) {
            return new BetaDistribution(json.BetaDistribution.alpha[0], json.BetaDistribution.beta[0]);
        } else {
            throw "Malformed JSON provided to DistributionBuilder " + json;
        }
    } else if (json.GammaDistribution) {
        if(json.GammaDistribution.shape != null && json.GammaDistribution.scale != null) {
            return new GammaDistribution(json.GammaDistribution.shape[0], json.GammaDistribution.scale[0]);
        } else {
            throw "Malformed JSON provided to DistributionBuilder " + json;
        }
    } else if (json.StudentTDistribution) {
        if(json.StudentTDistribution.degreesOfFreedom != null && json.StudentTDistribution.nonCentralityParameter != null) {
            return new StudentTDistribution(json.StudentTDistribution.degreesOfFreedom[0], json.StudentTDistribution.nonCentralityParameter[0]);
        } else if(json.StudentTDistribution.degreesOfFreedom != null) {
            return new StudentTDistribution(json.StudentTDistribution.degreesOfFreedom[0]);
        } else {
            throw "Malformed JSON provided to DistributionBuilder " + json;
        }
    } else {
        throw "Malformed JSON provided to DistributionBuilder " + json;
    }
}

// 陳鍾誠增加的函數

jstat.Normal = function(mean, sd) {
	return new NormalDistribution(mean, sd);
}

jstat.LogNormal = function(meanlog, sdlog) {
	return new LogNormalDistribution(meanlog, sdlog);
}

jstat.Beta = function(alpha, beta) {
	return new BetaDistribution(alpha, beta);
}

jstat.Gamma = function(shape, scale) {
	return new GammaDistribution(shape, scale);
}

jstat.StudentT = function(degreesOfFreedom, mu) {
	return new StudentTDistribution(mean, sd);
}

module.exports = jstat;

